<?php

if (!defined('IN_XLP')) {
    exit('Access Denied!');
}

class weixinMsgApi {

    const API_URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin';
    const AUTH_URL = '/token?grant_type=client_credential&';
    const MEDIA_GET_URL = '/media/get?';
    const MEDIA_SET_URL = '/media/upload?';
    const QRCODE_CREATE_URL = '/qrcode/create?';
    const QR_SCENE = 0;
    const QR_LIMIT_SCENE = 1;
    const QRCODE_IMG_URL = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=';
    const USER_GET_URL = '/user/get?';
    const USER_INFO_URL = '/user/info?';
    const GROUP_GET_URL = '/groups/get?';
    const GROUP_CREATE_URL = '/groups/create?';
    const GROUP_UPDATE_URL = '/groups/update?';
    const GROUP_MEMBER_UPDATE_URL = '/groups/members/update?';
    const CUSTOM_SEND_URL = '/message/custom/send?';
    const MASS_SEND_URL = '/message/mass/send?';
    const MASS_DELETE_URL = '/message/mass/delete?';
    const TEMPLATE_MESSAGE_URL = '/message/template/send?';
    const SHORT_URL_URL = '/shorturl?';

    private $appid;
    private $appsecret;
    private $accessToken;
    public $errCode = 40001;
    public $errMsg = 'no access';
    private $accessTokenPre = 'token/weixin/access_token_';

    public function __construct($options = array()) {
        $this->appid = isset($options['appid']) ? $options['appid'] : '';
        $this->appsecret = isset($options['appsecret']) ? $options['appsecret'] : '';
        if (!$this->appid || !$this->appsecret) {
            $userInfo = F('weixin/setting');
            if (isHave($userInfo['user_info']['wx_AppId']) && isHave($userInfo['user_info']['wx_AppSecret'])) {
                $this->appid = $userInfo['user_info']['wx_AppId'];
                $this->appsecret = $userInfo['user_info']['wx_AppSecret'];
            } else {
                showError('api Error:请设置appid和appsecret');
            }
        }
    }

    /**
     * 删除验证数据
     * @param string $appid
     */
    public function resetAuth() {
        $this->accessToken = '';
        S($this->accessTokenPre . $this->appid, null);
        $this->checkAuth();
        return true;
    }

    /*
     * 检查是否需要重新获取accesstoken
     * 并记下日志
     */

    public function checkAccessToken() {
        if ($this->errCode == 40001) {
            $this->resetAuth();
            saveLog('accessToken/reset', 'weixin.api');
            return true;
        }
        return false;
    }

    /**
     * 根据媒体文件ID获取媒体文件
     * @param string $media_id 媒体文件id
     * @return raw data
     */
    public function getMedia($media_id) {
        if (!$media_id || (!$this->accessToken && !$this->checkAuth())) {
            return false;
        }
        $res = $this->downloadWeixinFile(self::API_URL_PREFIX . self::MEDIA_GET_URL . 'access_token=' . $this->accessToken . '&media_id=' . $media_id);
        $fileExt = '';
        if (isset($res['header']['content_type'])) {
            $extmatches = explode('/', $res['header']['content_type']);
            if (strExists($extmatches[1], ';')) {
                $extmatches = explode(';', $extmatches[1]);
                $fileExt = $extmatches[0];
            } else {
                $fileExt = $extmatches[1];
            }
        }
        switch ($fileExt) {
            case 'x-speex-with-header-byte':
                $fileExt = 'speex';
                break;
            case 'jpeg':
                $fileExt = 'jpg';
                break;
        }
        return array('type' => $fileExt, 'data' => $res['body'], 'size' => strlen($res['body']));
    }

    /*
     * 上传媒体文件到微信服务器
     * @param $filePath string 服务器文件相对路径
     * @param $fileType string 文件类型
     * @return array
     */

    public function uploadMedia($filePath, $fileType = 'image') {
        $fileRealPath = realpath($filePath);
        if (!$fileRealPath || !in_array($fileType, array('image', 'voice', 'video', 'thumb'))) {
            return array('status' => 0, 'msg' => '文件不存在或者类型不对');
        }
        if (!$this->accessToken && !$this->checkAuth()) {
            return array('status' => 0, 'msg' => 'token失效');
        }
        $result = $this->uploadWeixinFile(self::API_URL_PREFIX . self::MEDIA_SET_URL . 'access_token=' . $this->accessToken . '&type=' . $fileType, array('media' => '@' . $fileRealPath));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->uploadMedia($filePath, $fileType);
                }
                return array('status' => 0, 'msg' => $this->errMsg);
            }
            return $json;
        }
        return array('status' => 0, 'msg' => $result);
    }

    /**
     * 创建二维码ticket
     * @param int $scene_id 自定义追踪id
     * @param int $type 0:临时二维码；1:永久二维码(此时expire参数无效)
     * @param int $expire 临时二维码有效期，最大为1800秒
     * @return array('ticket'=>'qrcode字串','expire_seconds'=>1800)
     */
    public function getQRCode($scene_id, $type = 0, $expire = 1800) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $data = array(
            'action_name' => $type ? 'QR_LIMIT_SCENE' : 'QR_SCENE',
            'expire_seconds' => $expire,
            'action_info' => array('scene' => array('scene_id' => $scene_id))
        );
        if ($type == 1) {
            unset($data['expire_seconds']);
        }
        $result = getHttp(self::API_URL_PREFIX . self::QRCODE_CREATE_URL . 'access_token=' . $this->accessToken, self::json_encode($data));
       saveLog('qrcode/get',array('token'=>$this->accessToken,'info'=>$data,'data'=>self::json_encode($data),'res'=>$result));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->getQRCode($scene_id, $type, $expire);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 获取二维码图片
     * @param string $ticket 传入由getQRCode方法生成的ticket参数
     * @return string url 返回http地址
     */
    public function getQRUrl($ticket) {
        return self::QRCODE_IMG_URL . $ticket;
    }

    /**
     * 发送客服消息
     * @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}}
     * @return boolean|array
     */
    public function sendCustomMessage($data) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::CUSTOM_SEND_URL . 'access_token=' . $this->accessToken, self::json_encode($data));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->sendCustomMessage($data);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 发送群发消息
     * @param array $data 消息结构
     * @example array('touser'=>array("oR5Gjjl_eiZoUpGozMo7dbBJ362A", "oR5Gjjo5rXlMUocSEXKT7Q5RQ63Q"),'msgtype'=>'text','text'=>array('content'=>'hello from boxer.'));
     * {"touser": ["oR5Gjjl_eiZoUpGozMo7dbBJ362A", "oR5Gjjo5rXlMUocSEXKT7Q5RQ63Q" ], "msgtype": "text", "text": { "content": "hello from boxer."}}
     * @return boolean|array
     * @example  {"errcode":0,"errmsg":"send job submission success","msg_id":34182}
     */
    public function sendMassMessage($data) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::MASS_SEND_URL . 'access_token=' . $this->accessToken, self::json_encode($data));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->sendMassMessage($data);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /*
     * 发送模板消息
     */

    public function sendTplMessage($data) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::TEMPLATE_MESSAGE_URL . 'access_token=' . $this->accessToken, self::json_encode($data));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                $this->dataInfo = self::json_encode($data);
                if ($this->checkAccessToken()) {
                    return $this->sendTplMessage($data);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 删除群发消息
     * 只有已经发送成功的消息才能删除删除消息只是将消息的图文详情页失效，已经收到的用户，还是能在其本地看到消息卡片。 另外，删除群发消息只能删除图文消息和视频消息，其他类型的消息一经发送，无法删除。
     * @param msgid int 群发消息msgid
     * @return array
     */
    public function delMassMessage($msgid) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::MASS_DELETE_URL . 'access_token=' . $this->accessToken, self::json_encode(array('msgid' => $msgid)));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->delMassMessage($msgid);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 获取关注者详细信息
     * @param string $openid
     * @return array
     */
    public function getUserInfo($openid) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::USER_INFO_URL . 'access_token=' . $this->accessToken . '&openid=' . $openid);
        if ($result) {
            $json = json_decode($result, true);
            if (isset($json['errcode']) && !empty($json['errcode'])) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->getUserInfo($openid);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /*
     * 生成短连接
     * @param string 要生成短连接的url
     * @return string
     */

    public function getShortUrl($url) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::SHORT_URL_URL . 'access_token=' . $this->accessToken, self::json_encode(array('action' => 'long2short', 'long_url' => $url)));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->getShortUrl($url);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 批量获取关注用户列表
     * @param unknown $next_openid
     */
    public function getUserList($next_openid = '') {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::USER_GET_URL . 'access_token=' . $this->accessToken . '&next_openid=' . $next_openid);
        if ($result) {
            $json = json_decode($result, true);
            if (isset($json['errcode']) && !empty($json['errcode'])) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->getUserList($next_openid);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 获取用户分组列表
     * @return boolean|array
     */
    public function getGroup() {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $result = getHttp(self::API_URL_PREFIX . self::GROUP_GET_URL . 'access_token=' . $this->accessToken);
        if ($result) {
            $json = json_decode($result, true);
            if (isset($json['errcode']) && !empty($json['errcode'])) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->getGroup();
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 新增自定分组
     * @param string $name 分组名称
     * @return boolean|array
     */
    public function createGroup($name) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $data = array(
            'group' => array('name' => $name)
        );
        $result = getHttp(self::API_URL_PREFIX . self::GROUP_CREATE_URL . 'access_token=' . $this->accessToken, self::json_encode($data));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->createGroup($name);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 更改分组名称
     * @param int $groupid 分组id
     * @param string $name 分组名称
     * @return boolean|array
     */
    public function updateGroup($groupid, $name) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $data = array(
            'group' => array('id' => $groupid, 'name' => $name)
        );
        $result = getHttp(self::API_URL_PREFIX . self::GROUP_UPDATE_URL . 'access_token=' . $this->accessToken, self::json_encode($data));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->updateGroup($groupid, $name);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 移动用户分组
     * @param int $groupid 分组id
     * @param string $openid 用户openid
     * @return boolean|array
     */
    public function updateGroupMembers($groupid, $openid) {
        if (!$this->accessToken && !$this->checkAuth()) {
            return false;
        }
        $data = array(
            'openid' => $openid,
            'to_groupid' => $groupid
        );
        $result = getHttp(self::API_URL_PREFIX . self::GROUP_MEMBER_UPDATE_URL . 'access_token=' . $this->accessToken, self::json_encode($data));
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && !empty($json['errcode']))) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                if ($this->checkAccessToken()) {
                    return $this->updateGroupMembers($groupid, $openid);
                }
                return false;
            }
            return $json;
        }
        return false;
    }

    /**
     * 微信api不支持中文转义的json结构
     * @param array $arr
     */
    static private function json_encode($arr) {
        $parts = array();
        $is_list = false;
        //Find out if the given array is a numerical array
        $keys = array_keys($arr);
        $max_length = count($arr) - 1;
        if (($keys [0] === 0) && ($keys [$max_length] === $max_length )) { //See if the first key is 0 and last key is length - 1
            $is_list = true;
            for ($i = 0; $i < count($keys); $i++) { //See if each key correspondes to its position
                if ($i != $keys [$i]) { //A key fails at position check.
                    $is_list = false; //It is an associative array.
                    break;
                }
            }
        }
        foreach ($arr as $key => $value) {
            if (is_array($value)) { //Custom handling for arrays
                if ($is_list) {
                    $parts [] = self::json_encode($value); /* :RECURSION: */
                } else {
                    $parts [] = '"' . $key . '":' . self::json_encode($value); /* :RECURSION: */
                }
            } else {
                $str = '';
                if (!$is_list) {
                    $str = '"' . $key . '":';
                }
                //Custom handling for multiple data types
                if (is_numeric($value) && $value < 2000000000) {
                    $str .= $value; //Numbers
                } elseif ($value === false) {
                    $str .= 'false'; //The booleans
                } elseif ($value === true) {
                    $str .= 'true';
                } else {
                    $str .= '"' . addslashes($value) . '"'; //All other things
                }
// :TODO: Is there any more datatype we should be in the lookout for? (Object?)
                $parts [] = $str;
            }
        }
        $json = implode(',', $parts);
        if ($is_list) {
            return '[' . $json . ']'; //Return numerical JSON
        }
        return '{' . $json . '}'; //Return associative JSON
    }

    /**
     * 通用auth验证方法，暂时仅用于菜单更新操作
     * @param string $appid
     * @param string $appsecret
     */
    public function checkAuth() {
        if (!$this->appid || !$this->appsecret) {
            saveLog('accessToken/lose', 'weixin.api');
            return false;
        }
        if ($this->accessToken) {
            return $this->accessToken;
        }
        $this->accessToken = S($this->accessTokenPre . $this->appid);
        if ($this->accessToken) {
            return $this->accessToken;
        }
        $result = getHttp(self::API_URL_PREFIX . self::AUTH_URL . 'appid=' . $this->appid . '&secret=' . $this->appsecret);
        if ($result) {
            $json = json_decode($result, true);
            if (!$json || (isset($json['errcode']) && $json['errcode'])) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                return false;
            }
            $this->accessToken = $json['access_token'];
            S($this->accessTokenPre . $this->appid, $json['access_token'], $json['expires_in'] - 100);
        }
        return $this->accessToken;
    }

    /*
     * 微信服务器下载文件
     * @param $url string 处理后的完整路径
     */

    private function downloadWeixinFile($url) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_NOBODY, 0);    //只取body头
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $package = curl_exec($ch);
        $httpinfo = curl_getinfo($ch);
        curl_close($ch);
        return array('header' => $httpinfo, 'body' => $package);
    }

    /*
     * 微信服务器上传文件
     */

    private function uploadWeixinFile($url, $data = null) {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($data)) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        curl_close($curl);
        return $output;
    }

}
